/* 2010/10/12 */
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace Cosmos.Windows.Forms.FileList.DynamicColumn
{
    /// <summary>
    /// Split up a file name into attributes.
    /// </summary>
    public class SpliterFileNameAttributeExtractor : IDynamicAttributeExtractor
    {
        private const string _fileNameHeadSymbol = "%";
        private const string _fileNameTailSymbol = "%";
        private const string _columnNameSymbol = "File_Name_Attribute_";

        private DynamicColumnDefinition[] _columnDefinitions;
        private string[] _separators;

        public SpliterFileNameAttributeExtractor(string pattern)
        {
            if (pattern == null || pattern == String.Empty)
                throw new ArgumentException("There is no description text.");

            // Check '<' and '>'.
            bool hasAttribute = false;
            Stack<object> braceStack = new Stack<object>();
            foreach (char aChar in pattern)
            {
                if (aChar == '<')
                    if (braceStack.Count == 1)
                        throw new ArgumentException("Angle bracket error.");
                    else
                    {
                        braceStack.Push(new object());
                        hasAttribute = true;
                    }
                else if (aChar == '>')
                    if (braceStack.Count == 0)
                        throw new ArgumentException("Angle bracket error.");
                    else
                        braceStack.Pop();
            }
            if (!hasAttribute)
                throw new ArgumentException("There should be at least one attribute.");


            // Split the file name into attributes and separators.
            List<string> separatorText = new List<string>();
            List<string> attributeText = new List<string>();

            string subText = String.Concat(_fileNameHeadSymbol, pattern, _fileNameTailSymbol);
            int charIndex = 0;
            while (true)
            {
                charIndex = subText.IndexOf("<");
                if (charIndex != -1)
                {
                    separatorText.Add(subText.Substring(0, charIndex));
                    subText = subText.Substring(charIndex + 1);

                    charIndex = subText.IndexOf(">");
                    attributeText.Add(subText.Substring(0, charIndex));
                    subText = subText.Substring(charIndex + 1);
                }
                else
                    break;
            }
            separatorText.Add(subText);

            // If the attributes are described correctly, create the AttributeColumnHeader.
            List<DynamicColumnDefinition> attributeList = new List<DynamicColumnDefinition>(attributeText.Count);
            foreach (string anAttribute in attributeText)
            {
                string[] attributeDescription = anAttribute.Split(':');
                if (attributeDescription.Length != 3)
                    throw new ArgumentException("An attribute description is not complete.");
                else
                {
                    string columnName = String.Concat(_columnNameSymbol, attributeDescription[0]);
                    string columnText = attributeDescription[1];
                    int columnWidth;
                    if (Int32.TryParse(attributeDescription[2], out columnWidth))
                        attributeList.Add(new DynamicColumnDefinition(columnName, columnText, columnWidth));
                    else
                        throw new ArgumentException("An attribute description is unavailable.");
                }
            }
            _columnDefinitions = attributeList.ToArray();

            // Simplify the separators, and if the separators are described correctly, store them.
            List<string> separatorList = new List<string>(separatorText.Count);
            foreach (string aSeparator in separatorText)
            {
                string currentSeparator = aSeparator;
                while (true)
                {
                    int theIndex;
                    if (currentSeparator.Contains("**"))
                        theIndex = currentSeparator.IndexOf("**");
                    else if (currentSeparator.Contains("*?"))
                        theIndex = currentSeparator.IndexOf("*?");
                    else if (currentSeparator.Contains("?*"))
                        theIndex = currentSeparator.IndexOf("?*");
                    else
                        break;

                    string preString = currentSeparator.Substring(0, theIndex);
                    string postString = currentSeparator.Substring(theIndex + 2);
                    currentSeparator = String.Concat(preString, "*", postString);
                }

                bool hasSpecifiedChar = false;
                foreach (char aChar in currentSeparator)
                {
                    if (aChar != '*' && aChar != '?')
                    {
                        hasSpecifiedChar = true;
                        break;
                    }
                }

                if (!hasSpecifiedChar)
                    throw new ArgumentException("Attribute separators should contain at least one specified char.");
                else if (currentSeparator.StartsWith("*") || currentSeparator.EndsWith("*"))
                    throw new ArgumentException("Attribute separators should not start or end with '*'.");
                else
                    separatorList.Add(currentSeparator);
            }
            _separators = separatorList.ToArray();
        }

        public static SpliterFileNameAttributeExtractor Parse(string pattern)
        {
            return new SpliterFileNameAttributeExtractor(pattern);
        }

        public static bool TryParse(string pattern, out SpliterFileNameAttributeExtractor attribute)
        {
            try
            {
                attribute = new SpliterFileNameAttributeExtractor(pattern);
                return true;
            }
            catch (Exception ex)
            {
                string exceptionMessage = ex.Message;
                attribute = null;
                return false;
            }
        }

        public void AddColumns(ListView listView)
        {
            if (listView == null)
                return;

            ClearColumns(listView);

            // Add file name attribute columns.
            foreach(DynamicColumnDefinition aColumn in _columnDefinitions)
            {
                listView.Columns.Add(aColumn.Name, aColumn.Text, aColumn.Width);
            }
        }

        public void ClearColumns(ListView listView)
        {
            if (listView == null)
                return; 

            // Remove all the file name attribute columns.
            List<string> removeColumnNames = new List<string>(listView.Columns.Count);
            foreach (ColumnHeader aColumn in listView.Columns)
            {
                if (aColumn.Name.Contains(_columnNameSymbol))
                    removeColumnNames.Add(aColumn.Name);
            }
            foreach (string aName in removeColumnNames)
            {
                listView.Columns.RemoveByKey(aName);
            }
        }

        public void AddSubItems(ListViewItem fileItem, string fileName)
        {
            if (fileItem == null)
                return;

            int attributeCount = _columnDefinitions.Length;
            string[] attributeSubItems = new string[attributeCount];

            //fileItem.SubItems.Clear();
            for (int i = 0; i < attributeCount; i++)
            {
                ListViewItem.ListViewSubItem subItem = new ListViewItem.ListViewSubItem();
                subItem.Name = _columnDefinitions[i].Name;
                subItem.Text = String.Empty;
                fileItem.SubItems.Add(subItem);
            }

            string subText = String.Concat(_fileNameHeadSymbol, fileName, _fileNameTailSymbol);
            for (int i = 0; i <= attributeCount; i++)
            {
                int currentIndex = 0;
                for (int j = 0; j < _separators[i].Length; j++)
                {
                    if (_separators[i][j] == subText[currentIndex])
                        currentIndex++;
                    else if (_separators[i][j] == '?')
                        currentIndex++;
                    else if (_separators[i][j] == '*')
                    {
                        currentIndex = subText.IndexOf(_separators[i][j + 1], currentIndex);
                        if (currentIndex == -1)
                            return;
                    }
                    else
                        return;
                }
                if (i == attributeCount)  // The last separator.
                    break;
                subText = subText.Substring(currentIndex);

                char specifiedChar = '*';
                int fuzzyCharCount = 0;
                for (int j = 0; j < _separators[i + 1].Length; j++)
                {
                    if (_separators[i + 1][j] == '*')
                        return;
                    else if (_separators[i + 1][j] == '?')
                        fuzzyCharCount++;
                    else
                    {
                        specifiedChar = _separators[i + 1][j];
                        break;
                    }
                }
                currentIndex = subText.IndexOf(specifiedChar);
                if (currentIndex == -1)
                    return;

                currentIndex -= fuzzyCharCount;
                if (currentIndex < 0)
                    return;

                attributeSubItems[i] = subText.Substring(0, currentIndex);
                subText = subText.Substring(currentIndex);
            }

            for (int i = 0; i < attributeSubItems.Length; i++)
            {
                fileItem.SubItems[i + 1].Text = attributeSubItems[i];
            }
            
        }
    }
}
